Solutions/Threat Intelligence (NEW)/Analytic Rules/URLEntity_EmailUrlInfo_Updated.yaml (62 lines of code) (raw):

id: 9e32e545-e60c-47de-9941-f9ca1ada0a42 name: TI Map URL Entity to EmailUrlInfo description: | 'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in EmailUrlInfo.' severity: Medium requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EmailUrlInfo - connectorId: ThreatIntelligence dataTypes: - ThreatIntelligenceIndicator - connectorId: ThreatIntelligenceTaxii dataTypes: - ThreatIntelligenceIndicator - connectorId: MicrosoftDefenderThreatIntelligence dataTypes: - ThreatIntelligenceIndicator queryFrequency: 1h queryPeriod: 14d triggerOperator: gt triggerThreshold: 0 tactics: - CommandAndControl relevantTechniques: - T1071 query: | let dt_lookBack = 1h; let ioc_lookBack = 14d; let EmailUrlInfo_ = materialize(EmailUrlInfo | where isnotempty(Url) | where TimeGenerated >= ago(dt_lookBack) | extend Url = tolower(Url) | extend EmailUrlInfo_TimeGenerated = TimeGenerated); let EmailUrls = EmailUrlInfo_ | distinct Url | summarize make_list(Url); let EmailUrlDomains = EmailUrlInfo_ | distinct UrlDomain | summarize make_list(UrlDomain); let EmailEvents_ = materialize(EmailEvents | where TimeGenerated >= ago(dt_lookBack)); let TI = materialize(ThreatIntelIndicators //extract key part of kv pair | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0))) | where IndicatorType == "url" | extend Url = ObservableValue | where isnotempty(Url) | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel) | extend IndicatorId = tostring(split(Id, "--")[2]) | where TimeGenerated >= ago(ioc_lookBack) | where (isnotempty(Url) or isnotempty(Url)) | where tolower(Url) in (EmailUrls) or tolower(Url) in (EmailUrlDomains) | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id | where IsActive == true and ValidUntil > now()); (union (TI | join kind=innerunique (EmailUrlInfo_) on Url), (TI | join kind=innerunique (EmailUrlInfo_) on $left.Url == $right.UrlDomain)) | where EmailUrlInfo_TimeGenerated < ValidUntil | summarize EmailUrlInfo_TimeGenerated = arg_max(EmailUrlInfo_TimeGenerated, *) by IndicatorId, Url | extend Description = tostring(parse_json(Data).description) | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels)) | project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, Type, ValidUntil, Confidence, Url, UrlDomain, UrlLocation, NetworkMessageId | extend timestamp = EmailUrlInfo_TimeGenerated | join kind=inner (EmailEvents_) on NetworkMessageId | where DeliveryAction !has "Blocked" | extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0]) entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: RecipientEmailAddress - identifier: Name columnName: Name - identifier: UPNSuffix columnName: UPNSuffix - entityType: URL fieldMappings: - identifier: Url columnName: Url version: 1.0.2 kind: Scheduled